#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "airkiss.h"
#include "router.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_smartconfig.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "internal/esp_wifi_internal.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "rom/ets_sys.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "driver/hw_timer.h"
#include "tcpip_adapter.h"
#include "smartconfig_ack.h"
#include "mbedtls/base64.h"
#include "autolink.h"
#include "mqtt.h"

extern uint8_t deviceInfo[100];

int sock_fd;
static const char *TAG = "AIThinkerDemo Log";
static xTaskHandle handleLlocalFind = NULL;
static EventGroupHandle_t wifi_event_group;
static const int CONNECTED_BIT     = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;
static const int AIRKISS_DONE_BIT  = BIT2;
//是否连接服务器
static bool isConnect2Server = false;

/*
 * @Description:  微信配网近场发现，可注释不要
 * @param:
 * @return:
*/
static void TaskCreatSocket(void *pvParameters)
{

	printf("TaskCreatSocket to create!\n");

	char rx_buffer[128];
	uint8_t tx_buffer[512];
	uint8_t lan_buf[300];
	uint16_t lan_buf_len;
	struct sockaddr_in server_addr;
	int sock_server; /* server socked */
	int err;
	int counts = 0;
	size_t len;

	sock_server = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock_server == -1)
	{
		printf("failed to create sock_fd!\n");
		vTaskDelete(NULL);
	}

	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // inet_addr("255.255.255.255");
	server_addr.sin_port = htons(LOCAL_UDP_PORT);

	err = bind(sock_server, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if (err == -1)
	{
		vTaskDelete(NULL);
	}

	//base64加密要发送的数据
	if (mbedtls_base64_encode(tx_buffer, strlen((char *)tx_buffer), &len, deviceInfo, strlen((char *)deviceInfo)) != 0)
	{
		printf("[xuhong] fail mbedtls_base64_encode %s\n", tx_buffer);
		vTaskDelete(NULL);
	}

	printf("[xuhong] mbedtls_base64_encode %s\n", tx_buffer);

	struct sockaddr_in sourceAddr;
	socklen_t socklen = sizeof(sourceAddr);
	while (1)
	{
		memset(rx_buffer, 0, sizeof(rx_buffer));
		int len = recvfrom(sock_server, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&sourceAddr, &socklen);

		ESP_LOGI(TAG, "IP:%s:%d", (char *)inet_ntoa(sourceAddr.sin_addr), htons(sourceAddr.sin_port));
		//ESP_LOGI(TAG, "Received %s ", rx_buffer);

		// Error occured during receiving
		if (len < 0)
		{
			ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
			break;
		}
		// Data received
		else
		{
			rx_buffer[len] = 0;												   // Null-terminate whatever we received and treat like a string
			airkiss_lan_ret_t ret = airkiss_lan_recv(rx_buffer, len, &akconf); //检测是否为微信发的数据包
			airkiss_lan_ret_t packret;
			switch (ret)
			{
			case AIRKISS_LAN_SSDP_REQ:

				lan_buf_len = sizeof(lan_buf);
				//开始组装打包
				packret = airkiss_lan_pack(AIRKISS_LAN_SSDP_NOTIFY_CMD, ACCOUNT_ID, tx_buffer, 0, 0, lan_buf, &lan_buf_len, &akconf);
				if (packret != AIRKISS_LAN_PAKE_READY)
				{
					ESP_LOGE(TAG, "Pack lan packet error!");
					continue;
				}
				ESP_LOGI(TAG, "Pack lan packet ok ,send: %s", tx_buffer);
				//发送至微信客户端
				int err = sendto(sock_server, (char *)lan_buf, lan_buf_len, 0, (struct sockaddr *)&sourceAddr, sizeof(sourceAddr));
				if (err < 0)
				{
					ESP_LOGE(TAG, "Error occured during sending: errno %d", errno);
				}
				else if (counts++ > COUNTS_BOACAST)
				{
					shutdown(sock_fd, 0);
					closesocket(sock_fd);
					handleLlocalFind = NULL;
					vTaskDelete(NULL);
				}
				break;
			default:
				break;
			}
		}
	}
}

void TaskRestartSystem(void *parm)
{
	router_wifi_clean_info();
	vTaskDelay(500 / portTICK_RATE_MS);
	esp_restart();
	vTaskDelete(NULL);
}

/*
 * @Description: 微信配网
 * @param:
 * @return:
*/
bool startAirkissTask()
{

	ESP_LOGI(TAG, "startAirkissTask");
	int ret = pdFAIL;
	if (handleLlocalFind == NULL)
		ret = xTaskCreate(TaskCreatSocket, "TaskCreatSocket", 1024 * 2, NULL, 6, &handleLlocalFind);

	if (ret != pdPASS)
	{
		printf("create airkiss thread failed.\n");
		return false;
	}
	else
	{
		return true;
	}
}

/*
 * @Description: 配网回调
 * @param:
 * @param:
 * @return:
*/
static void sc_callback(smartconfig_status_t status, void *pdata)
{
	switch (status)
	{
		case SC_STATUS_LINK:
		{
			wifi_config_t *wifi_config = pdata;
			ESP_LOGI(TAG, "SSID:%s", wifi_config->sta.ssid);
			ESP_LOGI(TAG, "PASSWORD:%s", wifi_config->sta.password);
			ESP_ERROR_CHECK(esp_wifi_disconnect());
			ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_config));
			ESP_ERROR_CHECK(esp_wifi_connect());

			router_wifi_save_info(wifi_config->sta.ssid, wifi_config->sta.password);
		}
		break;
		case SC_STATUS_LINK_OVER:
			ESP_LOGI(TAG, "SC_STATUS_LINK_OVER");
			//这里乐鑫回调目前在master分支已区分是否为微信配网还是esptouch配网，当airkiss配网才近场回调！
			if (pdata != NULL)
			{
				sc_callback_data_t *sc_callback_data = (sc_callback_data_t *)pdata;
				switch (sc_callback_data->type)
				{
				case SC_ACK_TYPE_ESPTOUCH:
					ESP_LOGI(TAG, "Phone ip: %d.%d.%d.%d", sc_callback_data->ip[0], sc_callback_data->ip[1], sc_callback_data->ip[2], sc_callback_data->ip[3]);
					ESP_LOGI(TAG, "TYPE: ESPTOUCH");
					xEventGroupSetBits(wifi_event_group, ESPTOUCH_DONE_BIT);
					break;
				case SC_ACK_TYPE_AIRKISS:
					ESP_LOGI(TAG, "TYPE: AIRKISS");
					xEventGroupSetBits(wifi_event_group, AIRKISS_DONE_BIT);
					break;
				default:
					ESP_LOGE(TAG, "TYPE: ERROR");
					xEventGroupSetBits(wifi_event_group, ESPTOUCH_DONE_BIT);
					break;
				}
			}

			break;
		default:
			break;
	}
}

/*
 * @Description:  一键配网
 * @param:
 * @return:
*/
void TaskSmartConfigAirKiss2Net(void *parm)
{
	EventBits_t uxBits;
	//判别是否自动连接
	bool isAutoConnect = routerStartConnect();
	//是的，则不进去配网模式，已连接路由器
	if (isAutoConnect)
	{
		ESP_LOGI(TAG, "Next connectting router.");
		vTaskDelete(NULL);
	}
	//否，进去配网模式
	else
	{
		ESP_LOGI(TAG, "into smartconfig mode");
		ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS));
		ESP_ERROR_CHECK(esp_smartconfig_start(sc_callback));
	}
	//阻塞等待配网完成结果
	while (1)
	{
		uxBits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT | AIRKISS_DONE_BIT, true, false, portMAX_DELAY);
		// 微信公众号配网完成
		if (uxBits & AIRKISS_DONE_BIT)
		{
			ESP_LOGI(TAG, "smartconfig over , start find device");
			esp_smartconfig_stop();

			//把设备信息通知到微信公众号
			if (handleLlocalFind == NULL)
				xTaskCreate(TaskCreatSocket, "TaskCreatSocket", 1024 * 2, NULL, 6, &handleLlocalFind);

			ESP_LOGI(TAG, "getAirkissVersion %s", airkiss_version());

			vTaskDelete(NULL);
		}
		// smartconfig配网完成
		if (uxBits & ESPTOUCH_DONE_BIT)
		{
			ESP_LOGI(TAG, "smartconfig over , but don't find device by airkiss...");
			esp_smartconfig_stop();
			vTaskDelete(NULL);
		}
		vTaskDelay(500 / portTICK_RATE_MS);
	}
}

/*
 * @Description:  系统的wifi协议栈回调
 * @param:
 * @param:
 * @return:
*/
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
	switch (event->event_id)
	{
	case SYSTEM_EVENT_STA_START:
		xTaskCreate(TaskSmartConfigAirKiss2Net, "TaskSmartConfigAirKiss2Net", 1024*2, NULL, 3, NULL);
		break;
	case SYSTEM_EVENT_STA_GOT_IP:
	{
		xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);

		int ret = pdFAIL;
		if (handleMqtt == NULL)
			ret = xTaskCreate(TaskXMqttRecieve, "TaskXMqttRecieve", 1024*4, NULL, 5, &handleMqtt);

		if (ret != pdPASS)
		{
			printf("create TaskXMqttRecieve thread failed.\n");
		}
		break;
	}

	case SYSTEM_EVENT_STA_DISCONNECTED:
		esp_wifi_connect();
		xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
		isConnect2Server = false;
		break;
	default:
		break;
	}
	return ESP_OK;
}

void SmartConfigInit()
{
	tcpip_adapter_init();
	wifi_event_group = xEventGroupCreate();
	ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

	wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

	ESP_ERROR_CHECK(esp_wifi_init(&cfg));
	ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
	ESP_ERROR_CHECK(esp_wifi_start());
}
